/**
 * 
 */
package geometry;

import java.awt.Polygon;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;

import util.Constants;

/**
 * 
 * This class models a line function by=ax+c. To be a legal line function, not
 * both a and b can be 0.
 * 
 * @author wu wei
 * 
 */
public class LineFunction implements Constants{
	/**
	 * count of existing line function in the system. used to assign unique line
	 * function id to a line function.
	 */
	static int lf_count = 0;
	/**
	 * unique id of this line function.
	 */
	private int uniqueLfId;
	private LineType lt;
	private double a, b, c;

	public LineFunction(double a, double b, double c, LineType lt) {
		if (a == 0 && b == 0) {
			throw new NotDefinedException();
		}
		this.assignUniLfId();
		this.a = a;
		this.b = b;
		this.c = c;
		this.lt = lt;
	}

	private void assignUniLfId() {
		LineFunction.lf_count++;
		this.uniqueLfId = lf_count;
	}

	public LineFunction(Point2D.Double p1, Point2D.Double p2)
			throws NotDefinedException {
		this.assignUniLfId();
		/**
		 * <pre>
		 * case x1==x2 && y1==y2
		 * 	Throw exception 
		 * case x1==x2
		 * 	YParallel line (x=-c), i.e. b=0, a =1, c = - x1  
		 * case y1==y2
		 * 	XParallel line (y=-c), i.e. a=0, b=1, c=y1 
		 * case otherwise
		 * 	normal line y=ax+c, i.e. b=1, a = (y2-y1)/(x2-x1), c= y1-ax1
		 *</pre>
		 */
		boolean xEqual = (p1.x == p2.x);
		boolean yEqual = (p1.y == p2.y);

		if (xEqual && yEqual) {
			throw new NotDefinedException();
		} else if (xEqual) {
			this.lt = LineType.YPARALLEL;
			this.b = 0;
			this.a = 1;
			this.c = p1.x * -1;
		} else if (yEqual) {
			this.lt = LineType.XPARALLEL;
			this.a = 0;
			this.b = 1;
			this.c = p1.y;
		} else {
			this.lt = LineType.NORMAL;
			this.b = 1;
			this.a = ((double) (p2.y - p1.y)) / (p2.x - p1.x);
			this.c = p1.y - this.a * p1.x;
		}
	}

	/**
	 * @return the a
	 */
	public double getA() {
		return a;

	}

	/**
	 * @return the line type
	 */
	public LineType getLineType() {
		return lt;
	}

	/**
	 * @return the b
	 */
	public double getB() {
		return b;
	}

	/**
	 * @return the c
	 */
	public double getC() {
		return c;
	}

	public double getYByX(double x) {
		double yvalue = 0;
		if (this.lt == LineType.XPARALLEL) {
			yvalue = this.c;
		}
		if (this.lt == LineType.YPARALLEL) {
			throw new NotDefinedException();
		}
		if (this.lt == LineType.NORMAL) {
			yvalue = a * x + c;
		}
		return yvalue;
	}

	public double getXByY(double y) {
		double xvalue = 0;
		if (this.lt == LineType.XPARALLEL) {
			throw new NotDefinedException();
		}
		if (this.lt == LineType.YPARALLEL) {
			xvalue = -1 * c;
		}
		if (this.lt == LineType.NORMAL) {
			xvalue = (y - c) / a;
		}
		return xvalue;
	}

	/**
	 * Get a random point on the line.
	 * 
	 * @return
	 */
	public Point2D.Double getARandomPointOnTheLine() {
		/**
		 * <pre>
		 * if line x-parallel
		 * 	y=c
		 * 	x = random x
		 * else if line y-parallel
		 * 	x = c*-1
		 * 	y = random y
		 * else
		 * 	x = random x
		 * 	y = get y by x
		 * end if
		 * return (x,y)
		 * </pre>
		 */
		double x, y;
		if (this.lt == LineType.XPARALLEL) {
			y = c;
			x = Math.random() * 100;
		} else if (this.lt == LineType.YPARALLEL) {
			x = c * -1;
			y = Math.random() * 100;
		} else {
			x = Math.random() * 100;
			y = this.getYByX(x);
		}
		return new Point2D.Double(x, y);
	}

	/**
	 * Check whether the given point is on this line.
	 * 
	 * @param point
	 * @return
	 */
	public boolean isOnLine(Point2D.Double point) {
		if (this.lt == LineType.XPARALLEL) {
			return (point.y == this.c);
		} else if (this.lt == LineType.YPARALLEL) {
			return (point.x == this.c * -1);
		} else {
			return (this.getYByX(point.x) == point.y);
		}
	}

	/**
	 * @param anotherLf
	 * @return null if this line does not intersect with the given line, the
	 *         intersection otherwise.
	 */
	public Intersection intersects(LineFunction anotherLf) {
		return Intersection.compIntersection(this, anotherLf);
	}

	/**
	 * check whether this line intersects with the given line.
	 * 
	 * @param anotherLf
	 * @return
	 */
	public boolean doesIntersect(LineFunction anotherLf) {
		Intersection ip = Intersection.compIntersection(this, anotherLf);
		if (ip == null) {
			return false;
		} else {
			return true;
		}
	}

	public int getUniqueLineFunctionId() {
		return this.uniqueLfId;
	}

	public boolean equals(LineFunction anotherLf) {
		return (this.uniqueLfId == anotherLf.uniqueLfId);
	}

	/**
	 * check whether the given polygon and q line on the different sides of the
	 * bisector.
	 * 
	 * @param plg
	 * @return
	 */
	public boolean isOnDiffSide(Polygon plg, Point2D.Double q) {
		/**
		 * the polygon and q are on different sides of bisector IFF a point in
		 * the polygon lies in the bisector's different side as q does.
		 */
		double x = 0;
		double y = 0;

		if (plg.npoints == 3) {
			// triangle, get centroid
			x = plg.xpoints[0] / 3 + plg.xpoints[1] / 3 + plg.xpoints[2] / 3;
			y = plg.ypoints[0] / 3 + plg.ypoints[1] / 3 + plg.ypoints[2] / 3;
		} else {
			x = (plg.xpoints[0] + plg.xpoints[2]) / 2;
			y = (plg.ypoints[0] + plg.ypoints[2]) / 2;
		}
		Point2D.Double p = new Point2D.Double(x, y);
		return this.isOnDiffSide(p, q);
	}

	/**
	 * check whether point p and q are on the different sides of the line.
	 * 
	 * @param line
	 * @param p
	 * @param q
	 * @return
	 */
	public boolean isOnDiffSide(Point2D.Double p, Point2D.Double q) {
		assert (!isOnLine(p));
		assert (!isOnLine(q));
		return isOnDiffSideImpl2(p, q);
	}

	private boolean isOnDiffSideImpl2(Point2D.Double p, Point2D.Double q) {
		LineSegment ls = new LineSegment(p, q);
		return ls.doesIntersect(this);
	}

	/**
	 * One implementation of isOnDiffSide.
	 * 
	 * @param line
	 * @param p
	 * @param q
	 * @return
	 */
	// private boolean isOnDiffSideImpl(Point2D.Double p, Point2D.Double q) {
	// Line2D.Double seg = this.getARandomSegment();
	// int qDeterm = ConvexHullHelpers.determinantSign(q, (Point2D.Double) seg
	// .getP1(), (Point2D.Double) seg.getP2());
	// int pDeterm = ConvexHullHelpers.determinantSign(p, (Point2D.Double) seg
	// .getP1(), (Point2D.Double) seg.getP2());
	// return (qDeterm != pDeterm);
	// }

	private Line2D.Double getARandomSegment() {
		Point2D.Double rp1 = this.getARandomPointOnTheLine();
		Point2D.Double rp2 = this.getARandomPointOnTheLine();
		while (rp2.equals(rp1)) {
			rp2 = this.getARandomPointOnTheLine();
		}
		return new Line2D.Double(rp1, rp2);
	}

	@Override
	public int hashCode() {
		return this.uniqueLfId;
	}

	public String toString() {
		return "line" + this.uniqueLfId;
	}

	public static double calcSlope(Point2D.Double p1, Point2D.Double p2) {
		if (p2.x == p1.x) {
			if (p2.y > p1.y) {
				return java.lang.Double.POSITIVE_INFINITY;
			}
			if (p2.y < p1.y) {
				return java.lang.Double.NEGATIVE_INFINITY;
			}
		}
		return (p2.y - p1.y) / (p2.x - p1.x);
	}

}
